package boosting;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import weka.classifiers.Classifier;
import weka.classifiers.Evaluation;
import weka.core.Instance;
import weka.core.Instances;

public class AdaBoost {

//	private Classifier[] weakClassifiers = null;
	private List<Classifier> weakClassifiers = null;
	private Classifier weakClassifier = null;
	private List<Double> weights = null;
	private double errorThreshold = 0.9;
	private double currentThreshold = 0.0;
	private int numberMinOfWeakClassifiers = 2;
	private int numberMaxOfWeakClassifiers = 10;
	private int currentNumberOfWeakClassifiers = 2;
	
	private int currentIteration = 0;
	
	private Instances trainingDataset = null;
	private int numberOfClasses = 0;
	
	
	//========================================================================================================================
	// constructor
	//========================================================================================================================
	public AdaBoost(Classifier weakClassifier, Instances trainingDataset, int numberOfClasses, double errorThreshold) {
		
		try {
			this.weakClassifier = Classifier.makeCopy(weakClassifier);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("error while making copy of WEAK classifier on: AdaBoost Constructor function");
			e.printStackTrace();
		}
		
		// cria numero minimo de classifiadores com pesos iguais (peso = 1)
		this.weakClassifiers = new ArrayList<Classifier>();
		this.weights = new ArrayList<Double>();
		for(int i=0;i<this.numberMinOfWeakClassifiers;i++){
			try {
				this.weakClassifiers.add(Classifier.makeCopy(this.weakClassifier));
				this.weights.add(1.0);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				System.out.println("error while making copy of WEAK classifier on weak list: AdaBoost Constructor function");
				e.printStackTrace();
			}
		}
		
		
		
		this.errorThreshold = errorThreshold;
		
		this.trainingDataset = trainingDataset;
		this.numberOfClasses = numberOfClasses;
	}
	//========================================================================================================================
	
	
	//========================================================================================================================
	// constroi o meta classificador AdaBoost
	//========================================================================================================================
	public void buildAdaBoost() {
		
		Instances newTrainSet = new Instances(trainingDataset,0,trainingDataset.numInstances());
		
		do {
			
			//
			if(currentThreshold < errorThreshold){
				
				try {
					weakClassifiers.add(Classifier.makeCopy(this.weakClassifier));
					currentNumberOfWeakClassifiers = weakClassifiers.size();
					weights.add(1.0);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					System.out
					.println("error while creating a new weak classifier on: buildAdaBoost()");
					e.printStackTrace();
				}
				
				
			}
			
			
			// treina classificadores
			for (int i = currentIteration; i < currentNumberOfWeakClassifiers; i++, currentIteration++) {
				try {
					weakClassifiers.get(i).buildClassifier(newTrainSet);
					
				} catch (Exception e) {
					// TODO Auto-generated catch block
					System.out.println("error while training weak classifier on: buildAdaBoost()  classifier Id: " + i);
					e.printStackTrace();
				}
//				System.out.println("TrainSet " + i + " Size = " + newTrainSet.numInstances());
				weights.set(i, buildWeight(newTrainSet, i));
				this.normalizeWeights();
				newTrainSet = buildNewTrainSet(newTrainSet, i);

			}
			// 
			int numCorrect = 0;
			for (int i = 0; i < trainingDataset.numInstances(); i++) {
				int pred = this.classifyInstance(trainingDataset.instance(i));
				if(pred == trainingDataset.instance(i).classValue())
					numCorrect++;
			}
			this.currentThreshold = (double) numCorrect / (double) this.trainingDataset.numInstances();
//			System.out.println("AdaBoost Accuracy on Training Set: " + currentThreshold + "  Number of Weak Classifiers: " + currentNumberOfWeakClassifiers);
			
			

		} while ((currentThreshold < errorThreshold) && (currentNumberOfWeakClassifiers < numberMaxOfWeakClassifiers));
	}
	//========================================================================================================================

		
	//========================================================================================================================
	// constroi novo dataset
	//========================================================================================================================
	private Instances buildNewTrainSet(Instances trainData, int idClassifier){
		Instances newSet = new Instances(trainData, 0, trainData.numInstances()); 
		
		int numCorrect = 0;
		
		for(int i=0;i<trainData.numInstances();i++){
			double pred = 0.0;
			try {
				 pred = weakClassifiers.get(idClassifier).classifyInstance(trainData.instance(i));
				 
			} catch (Exception e) {
				// TODO Auto-generated catch block
				System.out.println("error while classifying an instance in order to build a new train set on: buildNewTrainSet()");
				e.printStackTrace();
			}
			
			if(trainData.instance(i).classValue() == pred)
				numCorrect++;
			else
				newSet.add(trainData.instance(i)); // adiciona mais instancias que foram classificadas erroniamente ao proximo dataset de treino
			
		}
		
		return newSet;
	}
	//========================================================================================================================
	
	
	//========================================================================================================================
	// constroi peso para cada classificador
	//========================================================================================================================
	private Double buildWeight(Instances trainData, int idClassifier){
		int numCorrect = 0;
		
		
		
		for(int i=0;i<trainData.numInstances();i++){
			double pred = 0.0;
			try {
				pred = this.weakClassifiers.get(idClassifier).classifyInstance(trainData.instance(i));
				if(trainData.instance(i).classValue() == pred)
					numCorrect++;
				
			} catch (Exception e) {
				// TODO Auto-generated catch block
				System.out.println("error while classifying an instance in order to build the weight for weak classifier on: buildWeight()");
				e.printStackTrace();
			}
			
			
		}
		double acuracia = ((double) numCorrect / (double) trainData.numInstances());
		
		double epsilon = acuracia/(1-acuracia);
		double beta = Math.log(epsilon);
		
		Double weight = new Double(beta);
//		System.out.println("Weight of WeakClassifier " + idClassifier + " : " + weight);
		
		return weight;
		
	}
	//========================================================================================================================
	
	
	//========================================================================================================================
	// classifica uma instancia usando o meta classificador AdaBoost
	//========================================================================================================================
	public int classifyInstance(Instance instance){
		// guarda as predicoes
		List<Double> predicoes = new ArrayList<Double>();
		
		for(int i = 0; i < weakClassifiers.size(); i++){			
			try {
				predicoes.add(weakClassifiers.get(i).classifyInstance(instance));
			} catch (Exception e) {
				System.out.println("error while classifiy instance on: classifyInstance() AdaBoost class");
				e.printStackTrace();
			}			
		}
			
		return ApplyWeightedCombination(predicoes,this.numberOfClasses);
	}
	//========================================================================================================================
	
	
	//========================================================================================================================
	// weighted combination
	//========================================================================================================================
	public int ApplyWeightedCombination(List<Double> predicoes, int numberOfClasses){
		Double[][] decisionProfile = new Double[currentNumberOfWeakClassifiers][numberOfClasses];

		for (int i = 0; i < numberOfClasses; i++) {
			for (int j = 0; j < currentNumberOfWeakClassifiers; j++) {
				decisionProfile[j][i] = 0.0;
			}
		}

		for (int j = 0; j < currentNumberOfWeakClassifiers; j++) {
			decisionProfile[j][predicoes.get(j).intValue()] = 1.0;
		}

		Double[] prediction = new Double[numberOfClasses];
		
		for(int i = 0; i < numberOfClasses; i++){
			prediction[i] = 0.0;
		}
		
		for(int i = 0; i < numberOfClasses; i++){
			for(int j = 0; j < currentNumberOfWeakClassifiers; j++){
				prediction[i] += weights.get(j) * decisionProfile[j][i]; 
			}
		}
		
		Double max = prediction[0];
		int indexMax = 0;
		for(int i = 1; i < numberOfClasses; i++){
			if(max < prediction[i]){
				max = prediction[i];
				indexMax = i;
			}
		}
		
		return indexMax;
		
	}
	//========================================================================================================================
	
	
	//========================================================================================================================
	// normaliza pesos
	//========================================================================================================================
	private void normalizeWeights(){
		Double sum = new Double(0.0);
		
		for(int i=0;i < weights.size(); i++){
			sum += weights.get(i);
		}
		
		Double temp = new Double(0.0);
		for(int i=0;i < weights.size(); i++){
			temp = weights.get(i)/sum;
			weights.set(i, temp);
		}
	}
	//========================================================================================================================
	//
	//========================================================================================================================
	public int getCurrentNumberOfWeakClassifiers(){return this.currentNumberOfWeakClassifiers;}
	public double getCurrentAccuracy(){return this.currentThreshold;}
	//========================================================================================================================
}
